package com.specter.sure.core.impl;

import static com.specter.sure.core.AuthConsts.TOKEN_FROM_CACHE;
import static com.specter.sure.core.AuthConsts.TOKEN_FROM_COOKIE;
import static com.specter.sure.core.AuthConsts.TOKEN_FROM_HEADER;
import static com.specter.sure.core.AuthConsts.TOKEN_FROM_OAUTH2;
import static com.specter.sure.core.AuthConsts.TOKEN_FROM_SESSION;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.specter.mvc.view.json.JSON;
import com.specter.sure.core.AuthConfig;
import com.specter.sure.core.AuthConsts;
import com.specter.sure.core.AuthContext;
import com.specter.sure.core.AuthException;
import com.specter.sure.core.Authorized;
import com.specter.sure.core.AuthorizedProvider;
import com.specter.sure.core.StokenizorProvider;
import com.specter.utils.CookieUtils;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

/**
 * Note:本地认证提供机
 * 
 * @author Liang.Wang
 * @version Apr 20, 2011 12:12:09 AM
 *
 */
@Slf4j
@Component
public class AuthorizedProviderImpl implements AuthorizedProvider, InitializingBean {
	protected @Autowired AuthConfig PROP;
	protected @Autowired StokenizorProvider storage;
	private List<Pattern> noControlResource;// 不要求登录的资源

	/**
	 * 认证用户是否合法(含本地和认证中心)，验证顺序及认证优先级
	 * 
	 * 一、根据请求获取 JWT 的 TOKEN 并验证是否成功
	 * 
	 * 二、根据配置的认证中心信息，去远程验证登录信息
	 * 
	 * @throws IOException
	 */
	@Override
	public boolean validate() throws Exception {
		HttpServletRequest request = AuthContext.getRequest();

		// 1、本地通过request获取access_token, 用户第三方开发
		String jwt = request.getParameter(TOKEN_FROM_OAUTH2);

		// 2、本地使用通过header传值：单点登录使用
		if (StringUtils.isBlank(jwt)) {
			String auth = request.getHeader(TOKEN_FROM_HEADER);// "Authorization":"Bearer TOKEN"
			jwt = StringUtils.isBlank(auth) || auth.length() <= 7 ? null : auth.substring(7);
		}

		// 3、本地使用COOKIE进行校验(记住密码操作)
		if (StringUtils.isBlank(jwt)) {
			jwt = CookieUtils.getAESCookie(request, TOKEN_FROM_COOKIE);
		}

		// 4、本地使用SESSION登录的校验
		if (StringUtils.isBlank(jwt)) {
			jwt = (String) request.getSession().getAttribute(TOKEN_FROM_SESSION);
		}

		Map<String, String> map = storage.tokenValidate(jwt);
		if (map != null) {
			Authorized subject = new Authorized(JSON.parseTo(map.get("payload")));
			AuthContext.setUserDetail(subject);
		}

		return map != null;
	}

	@Override
	public String login(Authorized subject) throws AuthException {
		// 1、设置CONTEXT中的用户信息，标记为登录状态
		AuthContext.setUserDetail(subject);

		// 2、设置CACHE中的用户信息，标记为登录状态
		String key = TOKEN_FROM_CACHE + ":nauth:user:" + subject.getUseruuid() + ":object";
		storage.set(key, subject, PROP.STORAGE_SESSION_EXPIRE);

		// 3、生成JWT的TOKEN交给各个客户端使用
		return storage.tokenCreate(subject.toMap(), PROP.STORAGE_SESSION_EXPIRE);

		// 4、其他需要存储的内容各自自行处理（详见各个登录Controller）
	}

	/**
	 * 注销用户信息 清除票据
	 */
	@Override
	public String logix(Authorized subject) throws AuthException {
		// 1、设置CONTEXT中的用户信息，标记为登录状态
		AuthContext.setUserDetail(null);

		// 2、设置CACHE中的用户信息，标记为登录状态
		String key = TOKEN_FROM_CACHE + ":nauth:user:" + subject.getUseruuid() + ":object";
		storage.evict(key);

		return subject.getUseruuid();
	}

	@Override
	public String encode(String loginId, String password) {
		if (StringUtils.equals(PROP.AUTHORIZE_PASSWORD_ENCODER, AuthConsts.Encoder.MD5_SALT.toString())) {
			// 盐值加密，将用户名和密码连接在一起在做MD5
			return DigestUtils.md5Hex(new StringBuilder(loginId).append(password).toString());
		} else if (StringUtils.equals(PROP.AUTHORIZE_PASSWORD_ENCODER, AuthConsts.Encoder.MD5.toString())) {
			// 单纯的对密码进行MD5加密
			return DigestUtils.md5Hex(password);
		} else if (StringUtils.equals(PROP.AUTHORIZE_PASSWORD_ENCODER, AuthConsts.Encoder.TXT.toString())) {
			// 明文
			return password;
		}
		return null;
	}

	@Override
	public boolean isNoControlResource(String path) {
		if (noControlResource != null && !noControlResource.isEmpty()) {
			for (Pattern each : noControlResource) {
				Matcher matcher = each.matcher(path);
				if (matcher.matches()) {
					log.debug("$$$ OpenRes: {}", path);
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.noControlResource = new ArrayList<>();
		// 登录校验地址需要被设为不需要控制的页面
		String[] uncontrol = PROP.AUTHORIZE_URL_UNCONTROL;
		uncontrol = ArrayUtils.add(uncontrol, "/");
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_NAUTH_FAILURE);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_NAUTH_AUTHORIZE);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_SAUTH_AUTHORIZE);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_SAUTH_CALLBACK);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_OAUTH_TOKEN);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_OAUTH_VALID);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_OAUTH_ACCESS);
		uncontrol = ArrayUtils.add(uncontrol, AuthConsts.URL_OAUTH_AUTHORIZE);
		uncontrol = ArrayUtils.add(uncontrol, PROP.AUTHORIZE_URL_LOGIN);
		uncontrol = ArrayUtils.add(uncontrol, PROP.AUTHORIZE_URL_LOGOUT);
		uncontrol = ArrayUtils.add(uncontrol, PROP.AUTHORIZE_URL_FAILURE);
		uncontrol = ArrayUtils.add(uncontrol, "/serv/*.+");
		uncontrol = ArrayUtils.add(uncontrol, "/error/*.+");
		uncontrol = ArrayUtils.add(uncontrol, "/favicon.ico");

		// 正则表达式预编译，提高性能
		for (int i = 0; i < uncontrol.length; i++) {
			Pattern p = Pattern.compile(uncontrol[i]);
			this.noControlResource.add(p);
		}
	}
}
